package bsh;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import bsh.meta.FieldDescriptor;
import bsh.utils.reflection.OpenClassLoader;

/**
 * Plain implementation of {@link Trait}
 * 
 * @author takeshi
 */
public class TraitImpl implements Trait {

    private final List<BytecodeGenerator> instanceInit;

    private final List<FieldDescriptor> fields;

    private final List<? extends Trait> traits;

    private final byte[] interfaceBytecode, implementorBytecode;

    private final String interfaceInternalName, implementorInternalName;

    private final Map<String, Class<?>> localTypeImports;

    private final Collection<? extends String> localWildcardImports;

    private final Map<String, byte[]> dependentTypes;

    /**
     * Constructor
     * 
     * @param instanceInit the initializers
     * @param fields the fields
     * @param traits the inherited traits
     * @param interfaceBytecode the interface bytecode
     * @param implementorBytecode the implementor bytecode
     * @param interfaceInternalName the interface internal name
     * @param implementorInternalName the implementor internal name
     * @param localTypeImports local type imports
     * @param localWildcardImports local wildcard imports
     * @param dependentTypes collection of inner types
     */
    public TraitImpl(List<BytecodeGenerator> instanceInit, List<FieldDescriptor> fields, List<? extends Trait> traits,
            byte[] interfaceBytecode, byte[] implementorBytecode, String interfaceInternalName, String implementorInternalName,
            Map<String, Class<?>> localTypeImports, Collection<? extends String> localWildcardImports, Map<String, byte[]> dependentTypes) {
        super();
        this.dependentTypes = dependentTypes;
        this.localTypeImports = localTypeImports;
        this.localWildcardImports = localWildcardImports;
        this.instanceInit = instanceInit;
        this.fields = fields;
        this.traits = traits;
        this.interfaceBytecode = interfaceBytecode;
        this.implementorBytecode = implementorBytecode;
        this.interfaceInternalName = interfaceInternalName;
        this.implementorInternalName = implementorInternalName;
    }

    /**
     * {@inheritDoc}
     */
    public List<BytecodeGenerator> instanceInitializers() {
        return this.instanceInit;
    }

    /**
     * {@inheritDoc}
     */
    public List<FieldDescriptor> instanceFields() {
        return this.fields;
    }

    /**
     * {@inheritDoc}
     */
    public List<? extends Trait> implementedTraits() {
        return traits;
    }

    /**
     * {@inheritDoc}
     */
    public LoadedTrait load(OpenClassLoader loader) {
        List<LoadedTrait> loadedTraits = new ArrayList<LoadedTrait>(this.traits.size());
        for (Trait t : traits) {
            loadedTraits.add(t.load(loader));
        }

        loader.registerClass(implementorInternalName, implementorBytecode);
        loader.registerClass(interfaceInternalName, interfaceBytecode);
        for (Entry<String, byte[]> entry : this.dependentTypes.entrySet()) {
            loader.registerClass(entry.getKey(), entry.getValue());
        }
        try {
            for (Entry<String, byte[]> entry : this.dependentTypes.entrySet()) {
                Class<?> localType = Class.forName(entry.getKey().replace('/', '.'), false, loader);
                final String simpleName = localType.getSimpleName();
                if (simpleName != null) {
                    localTypeImports.put(getSimpleName(localType.getSimpleName()), localType);
                }
            }
            Class<?> traitInterface = Class.forName(interfaceInternalName.replace('/', '.'), false, loader);
            Class<?> implementor = Class.forName(implementorInternalName.replace('/', '.'), false, loader);
            return new LoadedTraitImpl(this, traitInterface, implementor, loadedTraits);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Returns the simple name of a class
     * 
     * @param name String the fully qualified class name
     * @return {@link String}
     */
    private static String getSimpleName(String name) {
        return name.substring(Math.max(name.lastIndexOf('.'), name.lastIndexOf('$')) + 1);
    }

    /**
     * @return the interfaceBytecode
     */
    public byte[] getInterfaceBytecode() {
        return interfaceBytecode;
    }

    /**
     * @return the implementorBytecode
     */
    public byte[] getImplementorBytecode() {
        return implementorBytecode;
    }

    /**
     * @return the interfaceInternalName
     */
    public String getInterfaceInternalName() {
        return interfaceInternalName;
    }

    /**
     * @return the implementorInternalName
     */
    public String getImplementorInternalName() {
        return implementorInternalName;
    }

    /**
     * {@inheritDoc}
     */
    public Map<String, Class<?>> allTypeImports() {
        Map<String, Class<?>> map = new HashMap<String, Class<?>>(this.localTypeImports);
        for (Trait trait : this.traits) {
            Set<Entry<String, Class<?>>> imports = trait.allTypeImports().entrySet();
            for (Entry<String, Class<?>> entry : imports) {
                Class<?> mapped = map.get(entry.getKey());
                if (mapped != null && mapped != entry.getValue()) {
                    throw new IllegalStateException("Ambiguous import for name: " + entry.getKey() + " types: " + mapped + " and "
                            + entry.getValue());
                }
                map.put(entry.getKey(), entry.getValue());
            }
        }
        return map;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends String> allWildcardImports() {
        Set<String> imports = new HashSet<String>(this.localWildcardImports);
        for (Trait trait : this.traits) {
            imports.addAll(trait.allWildcardImports());
        }
        return imports;
    }

}